package com.captjack.shiro.component;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @author Capt Jack
 * @date 2018/6/8
 */
@Component
public class CustomizeSessionDaoImpl extends EnterpriseCacheSessionDAO {

    /**
     * 日志
     */
    private static final Logger logger = LoggerFactory.getLogger(CustomizeSessionDaoImpl.class);

    /**
     * redis操作模版
     */
    private final RedisTemplate redisTemplate;

    /**
     * The Redis key prefix for the sessions
     */
    private static final String REDIS_KEY_PREFIX = "shiro-redis-session:";

    /**
     * 序列化使用的字符编码格式
     */
    private static final String DEFAULT_CHARSET = "UTF-8";

    /**
     * 是否更新session
     */
    private static final boolean IS_UPDATE_SESSION = false;

    @Autowired
    public CustomizeSessionDaoImpl(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = super.generateSessionId(session);
        super.assignSessionId(session, sessionId);
        logger.info("创建seesion,id=[{}]", sessionId);
        try {
            redisTemplate.opsForValue().set(getKey((String) sessionId), serializableSession(session), 30, TimeUnit.MINUTES);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        logger.info("获取seesion,id=[{}]", sessionId.toString());
        Session readSession = null;
        try {
            readSession = (Session) deSerializableSession((String) redisTemplate.opsForValue().get(getKey(sessionId.toString())));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return readSession;
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        if (!IS_UPDATE_SESSION) {
            return;
        }
        String sessionKey = session.getId().toString();
        logger.info("更新session,id=[{}]", sessionKey);
        try {
            redisTemplate.opsForValue().set(getKey(sessionKey), serializableSession(session), 30, TimeUnit.MINUTES);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    @Override
    public void delete(Session session) {
        String sessionKey = session.getId().toString();
        logger.info("删除session,id=[{}]", sessionKey);
        try {
            redisTemplate.delete(getKey(sessionKey));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    @Override
    public Collection<Session> getActiveSessions() {
        return Collections.emptySet();
    }

    /**
     * 拼接redis存储的key
     *
     * @param originalKey session原生key
     * @return 拼装后的key
     */
    private String getKey(String originalKey) {
        return REDIS_KEY_PREFIX + originalKey;
    }

    /**
     * 获取序列化之前的对象
     *
     * @param str 序列化之后的字符串
     * @return 对象
     * @throws Exception 异常
     */
    private Object deSerializableSession(String str) throws Exception {
        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            byteArrayInputStream = new ByteArrayInputStream(Base64.decode(str));
            objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return objectInputStream.readObject();
        } finally {
            closeStream(objectInputStream, byteArrayInputStream);
        }
    }

    /**
     * 序列化
     *
     * @param obj 需要序列化的对象
     * @return 字符串
     * @throws Exception 异常
     */
    private String serializableSession(Object obj) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            return new String(Base64.encode(byteArrayOutputStream.toByteArray()), DEFAULT_CHARSET);
        } finally {
            closeStream(objectOutputStream, byteArrayOutputStream);
        }
    }

    /**
     * 依次关闭流
     *
     * @param closeables 需要关闭的流
     * @throws IOException 异常
     */
    private void closeStream(Closeable... closeables) throws IOException {
        for (Closeable closeAble : closeables) {
            if (closeAble != null) {
                closeAble.close();
            }
        }
    }

}
